﻿using System;
using System.IO;
using System.Threading.Tasks;
using WeDonekRpc.HttpApiGateway.Collect;
using WeDonekRpc.HttpApiGateway.FileUp.Interface;
using WeDonekRpc.HttpApiGateway.FileUp.Model;
using WeDonekRpc.HttpApiGateway.Interface;
using WeDonekRpc.HttpApiGateway.Model;
using WeDonekRpc.HttpService.Interface;
using WeDonekRpc.Helper;
namespace WeDonekRpc.HttpApiGateway.FileUp
{
    public enum BlockUpState
    {
        待上传 = 1,
        准备中 = 0,
        校验中 = 2,
        上传完成 = 3,
        上传失败 = 4
    }
    /// <summary>
    /// 分块任务
    /// </summary>
    internal class BlockUpTask : IBlockUpTask
    {
        private string _SaveDir;
        private static readonly byte _Zero = 0;

        private readonly IUpBlockFile _upEvent;
        private string _TaskKey;
        private FileInfo _StateFile;

        private FileStream _StateFileStream;
        private string _ErrorCode;

        private readonly UpConfig _Config;

        private FileInfo _UpFile;

        private volatile BlockUpState _UpState = BlockUpState.待上传;
        private object _UpResult;
        private volatile int _TimeOut = HeartbeatTimeHelper.HeartbeatTime;

        public BlockUpTask (IUpBlockFile upEvent, string serviceName, UpConfig config)
        {
            this._Config = config;
            this.TaskId = Guid.NewGuid().ToString("N");
            this._upEvent = upEvent;
            this.ServerName = serviceName;
        }
        public string TaskId
        {
            get;
        }

        public string ServerName { get; }

        public string TaskKey => this._TaskKey;

        public int TimeOut => this._TimeOut;

        private UpFileForm _File;
        public void Load (IApiService service)
        {
            UpBasicFile file = this._upEvent.Init(service, this);
            _ = Task.Factory.StartNew(() =>
            {
                this._load(file);
            });
        }
        private void _load (UpBasicFile file)
        {
            this._File = new UpFileForm
            {
                alreadyUp = 0,
                blockSize = this._Config.BlockUpSize,
                fileName = file.FileName,
                fileMd5 = file.FileMd5.ToLower(),
                fileSize = file.FileSize
            };
            this._TaskKey = string.Concat(file.FileMd5, "_", file.DirName).GetMd5();
            this._UpState = BlockUpState.准备中;
            string zIndex = "ZoneIndex_" + Tools.GetZoneIndex(this._File.fileMd5);
            this._SaveDir = Path.Combine(HttpService.HttpService.Config.File.TempDirPath, "BlockUp", file.DirName, zIndex);
            if (!Directory.Exists(this._SaveDir))
            {
                _ = Directory.CreateDirectory(this._SaveDir);
            }
            BlockUpCollect.SyncTask(this);
            this._LoadStateFile();
            this._InitFile();
            if (this._File.CheckIsComplate())
            {
                this._UpComplate();
                return;
            }
            else if (this._Config.SyncBlockLimit < this._File.fileSize)
            {
                _ = Task.Factory.StartNew(() =>
                {
                    this._LoadFile();
                });
            }
            else
            {
                this._LoadFile();
            }
        }

        /// <summary>
        /// 加载并校验已上传的文件
        /// </summary>
        private void _LoadFile ()
        {
            if (!this._UpFile.Exists)
            {
                using (FileStream stream = this._UpFile.Open(FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
                {
                    stream.SetLength(this._File.fileSize);
                }
                this._UpFile.Refresh();
                this._UpState = BlockUpState.待上传;
            }
            else if (!this._CheckFile())
            {
                this.UpError("gateway.http.up.file.check.error");
            }
            else
            {
                this._UpState = BlockUpState.待上传;
            }
        }
        /// <summary>
        /// 检查临时文件
        /// </summary>
        /// <returns></returns>
        private bool _CheckFile ()
        {
            if (this._File.fileSize != this._UpFile.Length)
            {
                return false;
            }
            else if (this._File.alreadyUp > 0 && !this._File.cs.IsExists(c => c != _Zero))
            {
                this._File.Reset(this._StateFileStream);
                return true;
            }
            else if (this._Config.MaxCheckBlock == 0)
            {
                return true;
            }
            int end = this._Config.MaxCheckBlock == -1 ? int.MaxValue : this._Config.MaxCheckBlock;
            byte[] cs = this._File.cs;
            using (FileStream stream = this._UpFile.Open(FileMode.Open))
            {
                for (int i = 0; i < cs.Length; i++)
                {
                    byte v = cs[i];
                    if (v != 0)
                    {
                        if (Tools.CS(stream, i * this._File.blockSize, this._File.blockSize) != v)
                        {
                            return false;
                        }
                        else if (--end == 0)
                        {
                            break;
                        }
                    }
                }
            }
            return true;
        }
        /// <summary>
        /// 初始化临时文件
        /// </summary>
        private void _InitFile ()
        {
            string name = string.Concat(this._File.fileMd5, Path.GetExtension(this._File.fileName) + ".tmp");
            string path = Path.Combine(this._SaveDir, name);
            this._UpFile = new FileInfo(path);
            if (!this._UpFile.Exists && this._File.alreadyUp != 0)
            {
                this._File.Reset(this._StateFileStream);
            }
        }
        /// <summary>
        /// 获取上传状态
        /// </summary>
        /// <returns></returns>
        public BlockUpSate GetUpState ()
        {
            this._TimeOut = HeartbeatTimeHelper.HeartbeatTime;
            if (this._UpState == BlockUpState.上传完成)
            {
                return new BlockUpSate
                {
                    UpState = this._UpState,
                    Result = this._UpResult
                };
            }
            else if (this._UpState == BlockUpState.准备中)
            {
                return new BlockUpSate
                {
                    UpState = this._UpState
                };
            }
            else if (this._UpState == BlockUpState.上传失败)
            {
                throw new ErrorException(this._ErrorCode);
            }
            else
            {
                return new BlockUpSate
                {
                    Block = new BlockDatum
                    {
                        FileSize = this._File.fileSize,
                        AlreadyUp = this._File.alreadyUp,
                        BlockSize = this._File.blockSize,
                        NoUpIndex = this._File.cs.FindAllIndex(a => a == _Zero)
                    },
                    UpState = this._UpState
                };
            }
        }
        public Stream GetStream (int index)
        {
            this._TimeOut = HeartbeatTimeHelper.HeartbeatTime;
            return new AgentStream(this._UpFile, index * this._File.blockSize);
        }
        /// <summary>
        /// 写入文件
        /// </summary>
        /// <param name="file"></param>
        /// <param name="index"></param>
        public void WriteUpFile (IUpFile file, int index)
        {
            this._TimeOut = HeartbeatTimeHelper.HeartbeatTime;
            if (this._File.WriteAlreadyUp(this._StateFileStream, index, (int)file.FileSize, file.FileCs))
            {
                this._UpComplate();
            }
        }
        /// <summary>
        /// 上传文件
        /// </summary>
        private void _UpComplate ()
        {
            this._UpState = BlockUpState.校验中;
            _ = Task.Run(() =>
            {
                this.Dispose();
                if (this._UpComplateCheck())
                {
                    this._upEvent.Complate(new UpFileResult(this));
                }
            });
        }
        private bool _UpComplateCheck ()
        {
            string md5 = Tools.GetFileMd5(this._UpFile).ToLower();
            if (md5 != this._File.fileMd5)
            {
                this.UpError("gateway.http.up.file.error");
                return false;
            }
            return true;
        }
        /// <summary>
        /// 加载状态文件
        /// </summary>
        private void _LoadStateFile ()
        {
            string name = string.Concat(this._File.fileMd5, Path.GetExtension(this._File.fileName) + ".upState");
            this._StateFile = new FileInfo(Path.Combine(this._SaveDir, name));
            if (!this._StateFile.Exists)
            {
                this._createTempFile();
            }
            else
            {
                this._initTempFile();
            }
        }
        private void _initTempFile ()
        {
            this._StateFileStream = this._StateFile.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
            this._File.Init(this._StateFileStream);
        }
        private void _createTempFile ()
        {
            if (!this._StateFile.Directory.Exists)
            {
                this._StateFile.Directory.Create();
            }
            this._StateFileStream = this._StateFile.Open(FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
            this._File.Create(this._StateFileStream);
        }

        public void UpComplate<T> (T result)
        {
            this._UpResult = result;
            this._UpState = BlockUpState.上传完成;
        }

        public void UpError (string error)
        {
            this._ErrorCode = error;
            this._UpState = BlockUpState.上传失败;
            this.Dispose();
        }

        public FileInfo Save (string path)
        {
            path = HttpService.HttpService.GetFileSavePath(path);
            FileInfo file = new FileInfo(path);
            if (file.Exists)
            {
                return file;
            }
            else if (!file.Directory.Exists)
            {
                file.Directory.Create();
            }
            this._UpFile.MoveTo(path);
            file.Refresh();
            return file;
        }

        public bool CheckBlockIndex (int blockIndex)
        {
            return this._File.cs[blockIndex] == _Zero;
        }

        public void Dispose ()
        {
            if (this._StateFileStream != null)
            {
                this._StateFileStream.Close();
                this._StateFileStream.Dispose();
            }
        }
    }
}
